#ifndef SHERIFFOS2_CAN_H
#define SHERIFFOS2_CAN_H

// ----------- bsp -- can traits ----------

#include <cstring>
#include <bsp.h>

// ------------------ G474 -------------------
#ifdef STM32G4xx_HAL_FDCAN_H

template <size_t canId>
constexpr bool is_HAS_CAN_enabled() {
    switch (canId) {
        case 1:
            return BSP_HAS_CAN_FD1 or BSP_HAS_CAN1;
        case 2:
            return BSP_HAS_CAN_FD2 or BSP_HAS_CAN2;
        case 3:
            return BSP_HAS_CAN_FD3 or BSP_HAS_CAN3;
        default:
            return false;
    }
}

extern FDCAN_HandleTypeDef hfdcan1;
extern FDCAN_HandleTypeDef hfdcan2;
extern FDCAN_HandleTypeDef hfdcan3;

template <size_t canId>
constexpr auto get_CanHandler() {
    static_assert(is_HAS_CAN_enabled<canId>());
    switch (canId) {
        case 1:
            return &hfdcan1;
        case 2:
            return &hfdcan2;
        case 3:
            return &hfdcan3;
        default:;
    }
}

using CanState = HAL_FDCAN_StateTypeDef;
using CanInitType = FDCAN_InitTypeDef;
using CanFilterType = FDCAN_FilterTypeDef;
using CanTxHeader = FDCAN_TxHeaderTypeDef;
using CanRxHeader = FDCAN_RxHeaderTypeDef;
using CanTxEventFifo = FDCAN_TxEventFifoTypeDef;
using CanHpMsgStatus = FDCAN_HpMsgStatusTypeDef;
using CanProtocolStatus = FDCAN_ProtocolStatusTypeDef;
using CanErrorCounters = FDCAN_ErrorCountersTypeDef;
using CanMsgRamAddress = FDCAN_MsgRamAddressTypeDef;
using CanHandler = FDCAN_HandleTypeDef;
using CanCallbackId = HAL_FDCAN_CallbackIDTypeDef;

inline void CanRegisterRxFifo0Callback(CanHandler* hfdcan, pFDCAN_RxFifo0CallbackTypeDef pCallback) {
    HAL_FDCAN_RegisterRxFifo0Callback(hfdcan, pCallback);
}
inline void CanRegisterRxFifo1Callback(CanHandler* hfdcan, pFDCAN_RxFifo1CallbackTypeDef pCallback) {
    HAL_FDCAN_RegisterRxFifo1Callback(hfdcan, pCallback);
}
inline void CanRegisterTxEventFifoCallback(CanHandler* hfdcan, pFDCAN_TxEventFifoCallbackTypeDef pCallback) {
    HAL_FDCAN_RegisterTxEventFifoCallback(hfdcan, pCallback);
}
inline auto CanGetTxFifoFreeLevel(CanHandler* hfdcan) { return HAL_FDCAN_GetTxFifoFreeLevel(hfdcan); }
inline void CanGetRxMessage(CanHandler* hfdcan, uint32_t RxLocation, CanRxHeader* pRxHeader, uint8_t* pRxData) {
    HAL_FDCAN_GetRxMessage(hfdcan, RxLocation, pRxHeader, pRxData);
}
inline auto CanGetFifi0RxMsgCount(CanHandler* hfdcan) {
    return (hfdcan->Instance->RXF0S & FDCAN_RXF0S_F0FL) >> FDCAN_RXF0S_F0FL_Pos;
}
inline auto CanGetTxEvent(CanHandler* hfdcan, CanTxEventFifo& m_tx_event_) {
    return HAL_FDCAN_GetTxEvent(hfdcan, &m_tx_event_);
}

class CanTraits {
   public:
    uint8_t rx_data_[64]{};
    CanRxHeader rxHeader_{};
    CanTxEventFifo txEvent_{};
    struct Data {
        CanTxHeader header;
        uint8_t buffer[64]{};

        Data(CanTxHeader const& header, uint8_t const* buffer) : header(header) {
            switch (header.DataLength) {
                case FDCAN_DLC_BYTES_0:
                case FDCAN_DLC_BYTES_1:
                case FDCAN_DLC_BYTES_2:
                case FDCAN_DLC_BYTES_3:
                case FDCAN_DLC_BYTES_4:
                case FDCAN_DLC_BYTES_5:
                case FDCAN_DLC_BYTES_6:
                case FDCAN_DLC_BYTES_7:
                case FDCAN_DLC_BYTES_8:
                    memcpy(this->buffer, buffer, header.DataLength);
                    break;
                case FDCAN_DLC_BYTES_12:
                case FDCAN_DLC_BYTES_16:
                case FDCAN_DLC_BYTES_20:
                case FDCAN_DLC_BYTES_24:
                    memcpy(this->buffer, buffer, 12 + 4 * (header.DataLength - FDCAN_DLC_BYTES_12));
                    break;
                case FDCAN_DLC_BYTES_32:
                case FDCAN_DLC_BYTES_48:
                case FDCAN_DLC_BYTES_64:
                    memcpy(this->buffer, buffer, 32 + 16 * (header.DataLength - FDCAN_DLC_BYTES_32));
                    break;
                default:
                    return;
            }
        }
    };

    static auto transmit_imp(CanHandler* hfdcan, FDCAN_TxHeaderTypeDef* tx_header_ptr, uint8_t const* tx_buffer) {
        tx_header_ptr->TxEventFifoControl = FDCAN_STORE_TX_EVENTS;
        return HAL_FDCAN_AddMessageToTxFifoQ(hfdcan, tx_header_ptr, tx_buffer);
    }
};

inline void fdCanInit(FDCAN_HandleTypeDef* hfdcan) {
    FDCAN_FilterTypeDef sFilterConfig;
    sFilterConfig.IdType = FDCAN_STANDARD_ID;
    sFilterConfig.FilterIndex = 0;
    sFilterConfig.FilterType = FDCAN_FILTER_MASK;
    sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
    sFilterConfig.FilterID1 = 0x0000;
    sFilterConfig.FilterID2 = 0x0000;

    if (HAL_FDCAN_ConfigFilter(hfdcan, &sFilterConfig) != HAL_OK) {
        Error_Handler();
    }
    if (HAL_FDCAN_ConfigGlobalFilter(hfdcan, FDCAN_ACCEPT_IN_RX_FIFO0, FDCAN_ACCEPT_IN_RX_FIFO0, FDCAN_FILTER_REMOTE,
                                     FDCAN_FILTER_REMOTE) != HAL_OK) {
        Error_Handler();
    }

    sFilterConfig.IdType = FDCAN_EXTENDED_ID;
    sFilterConfig.FilterIndex = 0;
    sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
    sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
    sFilterConfig.FilterID1 = 0x00000000;
    sFilterConfig.FilterID2 = 0x1FFFFFFF;
    if (HAL_FDCAN_ConfigFilter(hfdcan, &sFilterConfig) != HAL_OK) {
        Error_Handler();
    }
    if (HAL_FDCAN_ConfigGlobalFilter(hfdcan, FDCAN_ACCEPT_IN_RX_FIFO0, FDCAN_ACCEPT_IN_RX_FIFO0, FDCAN_FILTER_REMOTE,
                                     FDCAN_FILTER_REMOTE) != HAL_OK) {
        Error_Handler();
    }

    // 板通使用 CAN FD
    if (hfdcan == &hfdcan3) {
        HAL_FDCAN_ConfigTxDelayCompensation(hfdcan, hfdcan->Init.DataPrescaler * hfdcan->Init.DataTimeSeg1, 0);
        HAL_FDCAN_EnableTxDelayCompensation(hfdcan);
    }

    if (HAL_FDCAN_Start(hfdcan) != HAL_OK) {
        Error_Handler();
    }
    if (HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_TX_EVT_FIFO_NEW_DATA, 0) != HAL_OK) {
        Error_Handler();
    }
    if (HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK) {
        Error_Handler();
    }
    if (HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_BUS_OFF, 0) != HAL_OK) {
        Error_Handler();
    }
}

#endif

#endif  // SHERIFFOS2_CAN_H
